from web import form
import json, hashlib, zipfile, os, glob, time
import web, utils, decompiler

#web.config.debug = False
render = web.template.render('templates/')
render_base = web.template.render('templates/', base='base')

urls = ( '/', 'Index',
	'/about', 'Index',
	'/copyright', 'Index',
	'/login', 'Login',
	'/upload', 'Uploader',
	'/decompile/([a-fA-F0-9]{32})/?', 'Decompiler',
	
	#'/busca/()(q=([a-zA-Z0-9]*))?', 'Buscador', #EL BUSCADOR SE HACE EN JS
	#'/report/([a-fA-F0-9]{32})/busca/(q=([a-zA-Z0-9]*))?', 'Buscador',
	'/report/([a-fA-F0-9]{32})/info/classes.json', 'JsonAllClasses',
	'/report/([a-fA-F0-9]{32})', 'RedirReport',
	'/report/([a-fA-F0-9]{32})/', 'Report',
	'/report/([a-fA-F0-9]{32})/(0x.[0-9a-fA-F]+|@.*/.*)', 'JsonRecursos',
	'/report/([a-fA-F0-9]{32})/res/(@.*/.*)', 'RecursosView',#TODO
	'/report/([a-fA-F0-9]{32})/(([a-zA-Z_][a-zA-Z_0-9$]*\.?)*)', 'View',

	)
app = web.application(urls, globals())

sesion = web.session.Session(app, web.session.DiskStore('sessions'))

########## LOGIN ZONE ##########
def check_sesion():
	if not web.config.debug and not sesion.get('logged_in', False):
		raise web.seeother('/login')

class Login:
	login_form = form.Form(
		form.Textbox('username'),
		form.Password('password'),
		form.Button('Login'),
		)

	def valid_user(self):
		i = web.input()
		return i.get('username') == i.get('password')

	def GET(self, out=None):
		if web.input().get('out') is not None:
			sesion.logged_in = False
			raise web.seeother('/')
		return render_base.login(self.login_form())

	def POST(self):
		log = self.login_form()
		if log.validates() and self.valid_user():
			sesion.logged_in = True
			raise web.seeother('/')
		log.valid = None
		return render_base.login(log)

########## FUNCTION ZONE ##########
def checkCache(md5):
	if web.config.debug: return
	last_mod = utils.getLastModifed(utils.REPORTS_DIR + md5)
	if 'HTTP_IF_MODIFIED_SINCE' in web.ctx.env and web.ctx.env['HTTP_IF_MODIFIED_SINCE'] == last_mod:
		raise web.notmodified()
	etag = utils.getEtag(last_mod)
	if 'HTTP_IF_NONE_MATCH' in web.ctx.env and web.ctx.env['HTTP_IF_NONE_MATCH'] == etag:
		raise web.notmodified()
	web.header('Last-Modified', last_mod)
	web.header('Etag', etag)

class Index:
	def GET(self):
		check_sesion()
		return render_base.index()

class Decompiler:
	def GET(self, md5):
		check_sesion()
		files = glob.glob(utils.FILE_UPLOAD_DIR + md5 + '.*')
		if files:
			dec = decompiler.Decompiler()
			dec.decompile(files[0])
			raise web.seeother('/report/'+md5+'/')
		else:
			return render_base.upload("El fichero no se encuentra en el sistema")

class RedirReport:
	def GET(self, md5):
		raise web.seeother('/report/'+md5+'/')
class Report:
	def GET(self, md5):
		check_sesion()
		checkCache(md5)
		if os.path.isdir(utils.REPORTS_DIR + md5):
			#TODO ver que pasa cuando hay un dex y un apk con el mismo md5
			sample = []
			if os.path.isfile(utils.REPORTS_DIR + md5 + "/sample.apk"):
				sample += ['<a href="/'+ utils.REPORTS_DIR + md5 +'/sample.apk" >Descargar muestra</a>']
			if os.path.isfile(utils.REPORTS_DIR + md5 + "/classes.dex"):
				sample += ['<a href="/'+ utils.REPORTS_DIR + md5 +'/classes.dex" >Descargar dex</a>']
			if os.path.isfile(utils.REPORTS_DIR + md5 + "/classes.dex.dex2jar.jar"):
				sample += ['<a href="/'+ utils.REPORTS_DIR + md5 +'/classes.dex.dex2jar.jar" >Descargar jar</a>']
			#TODO insertar enlace si es un dex y tengo el apk

			manifest = ""
			if os.path.isfile(utils.REPORTS_DIR + md5 +'/smali/AndroidManifest.xml'):
				with open(utils.REPORTS_DIR + md5 +'/smali/AndroidManifest.xml', 'r') as fp:
					manifest = fp.read()
					fp.close()
			recursos = utils.findRecursos(utils.REPORTS_DIR + md5 +'/smali/res/')
			return render_base.report(md5, sample, manifest, recursos)
		elif glob.glob(utils.FILE_UPLOAD_DIR + md5 + '.*'):
			raise web.seeother('/decompile/'+md5)
		else:
			return render_base.upload("El fichero no se encuentra en el sistema")

class JsonRecursos():
	def GET(self, md5, key):
		check_sesion()
		checkCache(md5)
		web.header('Content-Type', 'application/json')
		return utils.getRecurso(md5, key)

class JsonAllClasses():
	def GET(self, md5):
		check_sesion()
		checkCache(md5)
		ret = []
		clases = utils.findPackages(utils.REPORTS_DIR + md5 +'/ddx/')
		for pack in clases:
			ret += [{"pack":pack,"clases":clases[pack]}]
		web.header('Content-Type', 'application/json')
		return json.dumps(ret)

class RecursosView():
	def GET(self, md5, key):
		check_sesion()
		checkCache(md5)
		absolute_path = utils.getPathFromRecurso(md5, key);
		if absolute_path:
			if absolute_path.endswith(".xml"):
				file = ""
				with open(absolute_path, 'r') as fp:
					file = fp.read()
					fp.close()
				return render_base.view(md5, file, "xml:nocontrols",True)
			raise web.seeother(absolute_path[1:])
		return web.notfound()

class View():
	def GET(self, md5, clase, class_name):
		check_sesion()
		checkCache(md5)
		type = None
		file = clase.replace(".","/")
		
		if os.path.isfile(utils.REPORTS_DIR+md5+"/smali/smali/"+file+".smali"):
			type = "smali:nocontrols"
			file = utils.REPORTS_DIR+md5+"/smali/smali/"+file+".smali"
		elif os.path.isfile(utils.REPORTS_DIR+md5+"/ddx/"+file+".ddx"):
			type = "ddx:nocontrols"
			file = utils.REPORTS_DIR+md5+"/ddx/"+file+".ddx"
		else:
			return web.notfound()
		
		with open(file, 'r') as fp:
			file = None
			file = fp.read()
			fp.close()
		return render_base.view(md5, file, type, False)

class Uploader:
	def GET(self):
		check_sesion()
		return render_base.upload(None)

	def POST(self):
		check_sesion()
		ficheros = web.input(android_file={})
		error = "Solo se soportan ficheros APK y DEX"

		resumen = hashlib.md5()
		resumen.update(ficheros['android_file'].value)
		md5_file = resumen.hexdigest()
		#TODO ya tengo el fichero?

		magic = ficheros['android_file'].file.read(3)
		file = None
		if magic[:2] == "PK":
			file = utils.FILE_UPLOAD_DIR + md5_file + ".apk"
		elif magic == "dex":
			file = utils.FILE_UPLOAD_DIR + md5_file + ".dex"

		#TODO ya tengo el fichero?
		if file:
			with open(file, 'wb') as fp:
				fp.write(magic)
				fp.write(ficheros['android_file'].file.read())
				fp.close()
				if file.endswith(".apk"):
					error = "No es un fichero APK valido"
					if zipfile.is_zipfile(file):
						zip = zipfile.ZipFile(file, 'r')
						error_file = zip.testzip()
						if error_file is not None:
							error = "Existen fichero erroneos en el APK ("+error_file+")"
						else:
							elementos = zip.namelist()
							if "classes.dex" in elementos and "AndroidManifest.xml" in elementos:
								error = None
						zip.close()
				else:
					error = None
				if error:
					os.remove(file)
		if error:
			return render_base.upload(error)
		raise web.seeother('/report/'+md5_file)





def test():
	app.run()

if __name__ == "__main__":
	app.run()
